#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include "lcd.h"

struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;
void *frame_base = 0;
int fb_con = -1;

void fb_PrintFix(void)
{
  printf("\n---Fixed Screen infomation---\n");
  printf("Identification: %s\n", finfo.id);
  switch(finfo.visual){
  case FB_VISUAL_MONO01:
    printf("Visual: Monochr. 1=Black 0=White. \n");
    break;
  case FB_VISUAL_MONO10:
    printf("Visual: Monochr. 1=White 0=Black. \n");
    break;
  case FB_VISUAL_TRUECOLOR:
    printf("Visual: True color. \n");
    break;
  case FB_VISUAL_PSEUDOCOLOR:
    printf("Visual: Pesudo color. \n");
    break;
  case FB_VISUAL_DIRECTCOLOR:
    printf("Visual: Direct color. \n");
    break;
  case FB_VISUAL_STATIC_PSEUDOCOLOR:
    printf("Visual: Pesudo color readonly. \n");
    break;
  default:
    fprintf(stderr, "Visual type unrecognized. \n");
    return;
  }
  printf("Start of frame buffer memory: 0x%lx\n", finfo.smem_start);
  printf("Length of frame buffer memory: 0x%x\n", finfo.smem_len);
  printf("Length of a line: %u\n", finfo.line_length);
}

void fb_PrintVar(void)
{
  printf("\n---Variable Screen infomation---\n");
  printf("Visible resolution: %uX%u. \n", vinfo.xres, vinfo.yres);
  printf("Visual resolution: %uX%u. \n", vinfo.xres_virtual, vinfo.yres_virtual);
  printf("Offset of resolution: %uX%u. \n", vinfo.xoffset, vinfo.yoffset);
  printf("BPP: %u\n", vinfo.bits_per_pixel);
  printf("Bitfield: \n");
  printf("\tOffset\tLength\tMsb_right\n");
  printf("Red\t%u\t%u\t%u\n", vinfo.red.offset, vinfo.red.length, vinfo.red.msb_right);
  printf("Green\t%u\t%u\t%u\n", vinfo.green.offset, vinfo.green.length, vinfo.green.msb_right);
  printf("Blue\t%u\t%u\t%u\n", vinfo.blue.offset, vinfo.blue.length, vinfo.blue.msb_right);
}

void fb_Init(void)
{
  fb_con = open("/dev/fb0", O_RDWR, 0);
  if (fb_con < 0){
    fprintf(stderr, "fb_Init(): Can't open /dev/fb0.\n");
    return;
  }
  
  if (ioctl(fb_con, FBIOGET_FSCREENINFO, &finfo) < 0){
    fprintf(stderr, "fb_Init(): Can't get FSCREENINFO.\n");
    close(fb_con);
    return;
  }
  
  if (ioctl(fb_con, FBIOGET_VSCREENINFO, &vinfo) < 0){
    fprintf(stderr, "fb_Init(): Can't get VSCREENINFO.\n");
    close(fb_con);
    return;
  }
  
  fb_PrintFix();
  fb_PrintVar();

  frame_base = mmap(NULL, finfo.smem_len, PROT_READ|PROT_WRITE, MAP_SHARED, fb_con, 0);
  if (frame_base == (void *)-1){
    fprintf(stderr, "fb_Init(): Can't mmap.\n");
    close(fb_con);
    return;
  }
  printf("\nVideo memory address = 0x%x\n", (unsigned int)frame_base);
}

void fb_Release(void)
{
  if (fb_con < 0){
    fprintf(stderr, "fb_Release():The device /dev/fb0 has not been opened.\n");
    return;
  }
  
  munmap(frame_base, finfo.smem_len);
  
  if (fb_con)
    close(fb_con);
  
  return;
}

int fb_MakeColor(char red, char green, char blue)
{
  int color;
  unsigned int ro, rl, go, gl, bo, bl;
  
  ro = vinfo.red.offset;
  rl = vinfo.red.length;
  go = vinfo.green.offset;
  gl = vinfo.green.length;
  bo = vinfo.blue.offset;
  bl = vinfo.blue.length;
    
  red &= ((1<<rl)-1);
  green &= ((1<<gl)-1);
  blue &= ((1<<bl)-1);
  color = (red << ro | green << go | blue << bo);
  return color;
}

void fb_PutPixel(short x, short y, int color)
{
  void *currPoint;

  unsigned width = vinfo.xres_virtual;
  unsigned height = vinfo.yres_virtual;
  
  if (x < 0 || x >= width ||
      y < 0 || y >= height){
    fprintf(stderr, "fb_PutPixel(): Pixel out of screen range.\n");
    fprintf(stderr, "fb_PutPixel(): x = %d, y = %d\n", x, y);
    return;
  }
  
  currPoint = (char *)frame_base + y * finfo.line_length + (x * vinfo.bits_per_pixel)/8;
  *((short *)currPoint) = (color & 0xffff);
}

void fb_Clear(int color)
{
  memset(frame_base, (color & 0xffff), finfo.smem_len);
}
